0x01 题记
做pwn题32C3时遇到Stack-smashing Protection(SSP)栈保护机制,不懂的其利用原理,特查阅资料学习一下。
0x02 技术浅析
Stack-smashing Protection(SSP)栈保护机制是由GCC的”-fstack-protector” 选项提供提供的,GCC的SSP机制实现了两种类型的栈保护,其一:变量在栈中的顺序发生了改变;其二:启用了canary值检测栈EIP是否被修改。栈中局部变量的组织方式重排列即编译器将所有的局部数组变量放置在栈的高地址位,尽可能地将所有buffers放置在接近canary的位置上,且尽可能地远离各个变量指针,此时利用数组溢出方式无法覆盖其它关键变量如局部函数指针等,实例分析如下:
1 2 3 4 5 6 7 8
| int func(char *arg1, char *arg2) { int a; char b[10]; char c[2]; strcpy(b, arg1); strcpy (c, arg2); a = 1; return 0;}
|
启用SSP前后,上述代码的栈结构布局分别如图1, 2 所示。 不启用SSP保护机制,编译上述程序gcc –E exp.c –o exp.i,gcc –S exp.i,生成的汇编编码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func: pushl %ebp movl %esp, %ebp subl $24, %esp movl 8(%ebp), %eax ;将参数argv1拷贝到%eax处 movl %eax, 4(%esp) ;再将%eax存放的argv1拷贝到%esp+4 leal -14(%ebp), %eax ;将buff b[]地址拷贝到%eax movl %eax, (%esp) ;将%eax存放的b[]拷贝到%esp call strcpy ;调用strcpy函数拷贝(%esp,%esp+4),即(b[],argv1) movl 12(%ebp), %eax ;将参数argv2拷贝到%eax处 movl %eax, 4(%esp) ;再将%eax存放的argv2拷贝到%esp+4 leal -16(%ebp), %eax ;将buff c[]地址拷贝到%eax movl %eax, (%esp) ;将%eax存放的c[]拷贝到%esp call strcpy ;调用strcpy函数拷贝(%esp,%esp+4),即(c[],argv2) movl $1, -4(%ebp) ;将值1拷贝到%ebp-4处,注意:%ebp-4存放的地址即&a; movl $0, %eax leave ret
|
启用SSP保护机制,编译上述程序“gcc –E exp.c –o exp.i –fstack-protector”,“gcc –S exp.i –fstack-protector”,生成的汇编编码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| func: pushl %ebp movl %esp, %ebp subl $56, %esp movl 8(%ebp), %eax movl %eax, -36(%ebp) movl 12(%ebp), %eax movl %eax, -40(%ebp) movl %gs:20, %eax ;取出canary movl %eax, -4(%ebp) ;将canary放置在ebp-4处 xorl %eax, %eax movl -36(%ebp), %eax ;将参数argv1拷贝到%eax处 movl %eax, 4(%esp) ;再将%eax存放的argv1拷贝到%esp+4 leal -14(%ebp), %eax ;将buff b[]地址拷贝到%eax movl %eax, (%esp) ;将%eax存放的b[]拷贝到%esp call strcpy ;调用strcpy函数拷贝(%esp,%esp+4),即(b[],argv1) movl -40(%ebp), %eax ;将参数argv2拷贝到%eax处 movl %eax, 4(%esp) ;再将%eax存放的argv2拷贝到%esp+4 leal -16(%ebp), %eax ;将buff c[ ]地址拷贝到%eax movl %eax, (%esp) ;将%eax存放的c[ ]拷贝到%esp call strcpy ;调用strcpy函数拷贝(%esp,%esp+4),即(c[],argv2) movl $1, -20(%ebp) ;将值1拷贝到%ebp-20处,注意:%ebp-20存放的地址即&a; movl $0, %eax movl -4(%ebp), %edx ;取%ebp-4位置处地址,将其拷贝给%edx,即canary值 xorl %gs:20, %edx ;比较%gs:20与栈内保存的canary是否相等; je .L3 call __stack_chk_fail
|
事实上,编译器将所有局部函数指针都搬迁到了栈中已分配空间的低地址位,如程序exp.c在启用-fstack-protector编译后的汇编代码,编译器优化了栈结构布局,将b[10]、a[2]两个数组紧邻canary存放,而把变量a防止在栈顶位置。这种类型的防御措施非常关键,可有效降低攻击者使用各种shellcode 地址覆盖栈内局部函数指针从而实施攻击的可能性。GCC SSP栈保护机制中同样也使用了随机数“canary”探针,在局部变量与函数返回地址EIP之间插入一个canary探针,如图2所示。在函数的末尾代码中,内核校验栈内的canary值是否发生改变,若两个值不一致,则内核终止函数执行,如:
1 2 3 4 5 6 7 8 9
| prologue: movl %gs:20, %eax ;取出canary movl %eax, -4(%ebp) ;将canary放置在ebp-4处 . . . epilogue : movl -4(%ebp), %edx ;取%ebp-4位置处地址,将其拷贝给%edx,即canary值 xorl %gs:20, %edx ;比较%gs:20与栈内保存的canary是否相等; je .L3 call __stack_chk_fail
|
首先读取存放在%gs:0x20位置处的探针值,并插入到栈的指定位置,然后在函数指令末尾片段处重新读取事先插入的canary值,并将其与%gs:0x20位置处canary进行比较,若校验失败,GCC会调用__stack_chk_fail函数,输出错误消息中止运行。